package logic;

import java.util.Arrays;
import java.util.Collection;
import java.util.Hashtable;
import java.util.TreeSet;

import data.MovieComputedRating;
import data.MovieComputedRatingComparator;
import data.MovieTuple;
import data.RatingTuple;
import data.SystemData;
import data.UserTuple;
public class CorrelationRecAlgorithm implements Algorithm {

	private Hashtable<String, Double> _users;
	private MovieComputedRating[] _movies;
	private MovieComputedRatingComparator _comp;
	private Double _userAvg=0.0;

	public CorrelationRecAlgorithm(){
		_users = new Hashtable<String, Double>();
		_movies = new MovieComputedRating[0];
		_comp = new MovieComputedRatingComparator();
	}

	public MovieComputedRating[] compute(UserTuple user) {
		SystemData sd = SystemData.getInstance();
		//_userAvg = user.getAvg();
		_userAvg= sd.getAvg(user.get_name());
		Collection<MovieTuple> moviesCol =  sd.getAllMovies();
		/**
		 * step 1 - computing W(a,i) for user with each of all other users.
		 * inserting all w's to a Hashtable (_users) - key=user(i)_name, value=W(a,i)
		 */
		_users = computeUsers(sd, user, moviesCol);//now we have a list of all the users w's

		/**
		 * step 2- we are now inserting all movies + their p's to a Hashtable (_movies),
		 * where p is the computed rating for the original user.
		 */
		_movies = computeMovies(sd, user, moviesCol, _users);

		/**
		 * we will now sort _movies, then run over it,
		 * returning a list of n movies with the highest p's.
		 */
		Arrays.sort(_movies, _comp);//.sort(_movies, _comp);
		
		return _movies;
	
		
		//deleted by adi
	//	Vector<MovieTuple> ans= new Vector<MovieTuple>(_movies.length);
	/*	for (int j= _movies.length-1;j>=Math.max(0,_movies.length-11);j--){
			MovieTuple m = sd.get_movies().get(_movies[j].get_movieName());
			ans.add(m);

		}Carmel Version*/
		//new version by Eran
	/*	for (int j= _movies.length-1;j>=0;j--){
			MovieTuple m = sd.get_movies().get(_movies[j].get_movieName());
			ans.add(m);

		}
		return ans;
		*/
	}

	private Hashtable<String, Double> computeUsers(SystemData sd, UserTuple user, Collection<MovieTuple> moviesCol){
		Hashtable<String, Double> ans = new Hashtable<String, Double>();
		_userAvg = user.getAvg(); //for testing purposes
		/**
		 * 1st loop (most outer)- runs over all movie records
		 */
		for (MovieTuple movie : moviesCol){
			/**
			 * 2nd loop- runs over all user records who rated current movie
			 */
			for (RatingTuple rating : sd.getRatingsByMovie(movie.get_name())){
				UserTuple useri = rating.get_user();
				if(!(useri.equals(user)|| ans.containsKey(useri.get_name()))){
					Double w =0.0;
					double moneW =0.0;
					double mechaneV =0.0;
					double mechaneVi =0.0;
					/**
					 * 3rd loop - runs over all movies rated by current useri
					 */
					for (RatingTuple rate : useri.getCopyOfRatings()){
						//System.out.println(useri.get_name()+" movie: "+rate.get_movie().get_name());
						MovieTuple useriMovie = rate.get_movie();
						/**
						 * 4th loop - runs over original user movies
						 */
						for(RatingTuple userRate : useri.getCopyOfRatings()){
							/**
							 * computing only the movies that both original user
							 * and useri have rated
							 */
							if(userRate.get_movie().equals(useriMovie)){
								double userV = userRate.get_rating()-_userAvg;
								double useriV = rate.get_rating()-useri.getAvg();
								moneW += userV*useriV;
								mechaneV += userV*userV;
								mechaneVi += useriV*useriV;
								//System.out.println(useri.get_name()+" userV: "+userV+" useriV: "+useriV+" moneW: "+moneW+" mechaneV: "+mechaneV+" mechaneVi: "+mechaneVi);
								break;
							}
						}
					}
					Double mechaneW = Math.sqrt(mechaneV*mechaneVi);

					if (mechaneW!=0)
						w=moneW/mechaneW;
					else w=0.0;
					//System.out.println(useri.get_name()+" moneW: "+moneW+" mechaneW: "+mechaneW);
					ans.put(useri.get_name(), w);
				}
			}
		}
		return ans;
	}

	private MovieComputedRating[] computeMovies(SystemData sd, UserTuple user, Collection<MovieTuple> moviesCol,Hashtable<String, Double> users){
		TreeSet<RatingTuple> rateByUser2 = user.getCopyOfRatings();
		MovieComputedRating[] ans= new MovieComputedRating[moviesCol.size()-rateByUser2.size()];
		int i=0;
		_userAvg = user.getAvg(); //for testing purposes
		/**
		 * 1st loop (most outer)- runs over all movie records
		 */
//		System.out.println("enter loop");
		for(MovieTuple movie2 : moviesCol){
//			System.out.println("movie   "+movie2.get_name());
			double pVal=0.0;
			double k=0.0;
			boolean flag = false;
			for(RatingTuple userRate : user.getCopyOfRatings()){
				/**
				 * computing only the movies that both original user
				 * and useri have rated
				 */
				if(userRate.get_movie().equals(movie2)){
					flag=true;
					break;
				}
			}
			if(!flag){
				/**
				 * 2nd loop- runs over all user records who rated current movie
				 */
				for(RatingTuple rating : sd.getRatingsByMovie(movie2.get_name())){
					UserTuple useri2 = rating.get_user();
//					System.out.println(useri2.get_name());
					double useri2Vi= sd.getRating(useri2.get_name(), movie2.get_name()).get_rating();
					double useri2Avg= useri2.getAvg();
					double useri2FV= useri2Vi-useri2Avg;
//					System.out.println("pVal inloop before  "+ "user name  " + useri2.get_name() +"   "+ pVal);
//					System.out.println(users.get(useri2.get_name())*useri2FV);
					pVal= pVal+ users.get(useri2.get_name())*useri2FV;
//					System.out.println("pVal inloop  after  "+ "user name  " + useri2.get_name() +"   "+ pVal);
					k= k + users.get(useri2.get_name());
//					System.out.println("k "+k);
				}
				if(k!=0)
					k= 1/Math.abs(k);
				Double p = _userAvg+k*pVal;
				MovieComputedRating newMovie = new MovieComputedRating(movie2.get_name(), p);
				ans[i]=newMovie;
				i++;
			}
		}
		return ans;
	}

}
